﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using alib.Concurrency;
using alib.Debugging;
using alib.Enumerable;
using alib.String;
using alib.Collections.ReadOnly;

namespace agree.Pmrs
{
	using EdgeFlag = Edge.Flag;
#if PMRS
	public partial class PortableMrs : atomic_selfhost
	{
		class _tfs_walk_helper
		{
			public _tfs_walk_helper(PortableMrs pmrs, Restrictor r, Action<arr_tfs_entry> callback, Dictionary<String, String> rel_name_map = null, bool add_skolems = true)
			{
				this.pmrs = pmrs;
				this.r = r;
				this.callback = r.NoRestriction ? callback : filter_callback;
				this.rel_name_map = rel_name_map;
				this.add_skolems = add_skolems;

				this.var_to_mark = new Dictionary<atomic_base, int>();
				this.next_mark = 1;
				this.next_coref_mark = -1;

				_start();
			}

			PortableMrs pmrs;
			Restrictor r;
			Action<arr_tfs_entry> callback;
			Dictionary<String, String> rel_name_map;
			bool add_skolems;

			Dictionary<atomic_base, int> var_to_mark;
			int next_mark;
			public int next_coref_mark;

			FeatMgr ftm { get { return r.FeatMgr; } }


			void filter_callback(arr_tfs_entry ate)
			{
				if (!r[ate.i_feat])
					callback(ate);
			}

			void _start()
			{
				arr_tfs_entry ate = default(arr_tfs_entry);
				ate.mark = (short)next_mark++;

				ate.i_feat = _get_feat("hook");
				ate.e = _get_edge(pmrs.hook);
				callback(ate);

				ate.i_feat = _get_feat("rels");
				ate.e = _difflist_edge(pmrs.rels);
				callback(ate);

				if (pmrs.hcons.Count > 0)
				{
					ate.i_feat = _get_feat("hcons");
					ate.e = _difflist_edge(pmrs.hcons);
					callback(ate);
				}
			}

			Edge _difflist_edge(IEnumerable<atomic_base> ab)
			{
				arr_tfs_entry ate = default(arr_tfs_entry);
				Edge e_last;
				int m;

				var abz = ab.GetEnumerator();
				if (abz.MoveNext())
				{
					m = next_mark++;
					ate.e = _get_list_edge(abz, out e_last);
				}
				else
				{
					m = next_coref_mark--;
					ate.e = e_last = new Edge(ftm.im.tt_ne_list.EdgeFlag | EdgeFlag.Coreference, m);
				}

				ate.i_feat = _get_feat("list");
				ate.mark = (short)m;
				callback(ate);

				ate.i_feat = _get_feat("last");
				ate.e = e_last;
				callback(ate);

				EdgeFlag f;
				ftm.tu.TryGetTypeFlag(pmrs.mrsm.AgreeConfig.Types.TypesDiffList, out f);
				return new Edge(ftm.im.tt_dlist.EdgeFlag, ate.mark);
			}

			Edge _get_list_edge(IEnumerator<atomic_base> ab, out Edge e_last)
			{
				arr_tfs_entry ate = default(arr_tfs_entry);
				int m = next_mark++;
				ate.mark = (short)m;

				ate.i_feat = _get_feat("first");
				ate.e = _get_edge(ab.Current);
				callback(ate);

				ate.i_feat = _get_feat("rest");
				if (ab.MoveNext())
				{
					ate.e = _get_list_edge(ab, out e_last);
				}
				else
				{
					ate.e = e_last = new Edge(ftm.im.tt_list.EdgeFlag | EdgeFlag.Coreference, next_coref_mark--);
				}
				callback(ate);

				return new Edge(ftm.im.tt_ne_list.EdgeFlag, m);
			}

			short _get_feat(String s_feat)
			{
				FeatureInfo fi;
				if (!ftm.feat_map.TryGetValue(s_feat, out fi))
				{
					return -1;
					//String msg = String.Format("Couldn't get feature '{0}' from grammar '{1}'", s_feat.ToUpper(), g.Name);
					//throw new Exception(msg);
				}
				return (short)fi.i_feat;
			}

			short _get_feat(String s_feat, out EdgeFlag f_maximal)
			{
				FeatureInfo fi;
				if (!ftm.feat_map.TryGetValue(s_feat, out fi))
				{
					String msg = String.Format("Couldn't get feature '{0}' from grammar '{1}'", s_feat.ToUpper(), ftm.Name);
					throw new Exception(msg);
				}
				f_maximal = (EdgeFlag)fi.introduced_by.EdgeFlag;
				return (short)fi.i_feat;
			}

			void walk(int m, atomic_base ab)
			{
				if (m == 0)
					throw new Exception();
				arr_tfs_entry ate = default(arr_tfs_entry);
				ate.mark = (short)m;

				complex cplx = ab as complex;
				if (cplx != null)
				{
					foreach (var z in cplx.rgav)
					{
						ate.i_feat = _get_feat(z.Key);
						if (ate.i_feat != -1)
						{
							ate.e = _get_edge(z.Value);
							callback(ate);
						}
					}
				}
			}

			Edge _get_edge(atomic_base nab)
			{
				EdgeFlag f;
				atomic_selfhost ht = nab.HostType;
				if (nab is relation)
				{
					relation rel = (relation)nab;
					if (rel.IsCoreferenced || rel.rgav.Count == 0)
						throw new Exception();

					String[] rgs = rel.rgav.Keys.ToArray();

					f = ftm.im.GetMaximalTypeForFeature(_get_feat("pred")).EdgeFlag;

					for (int i = 0; i < rgs.Length; i++)
					{
						int i_feat = _get_feat(rgs[i]);
						//if (!ftm.tm.UnifyInTypeNoCoref(ref f, ftm.tm.GetMaximalTypeForFeature(i_feat).EdgeFlag))
						if ((f = ftm.tu.UnifyTypesFull(f, ftm.im.GetMaximalTypeForFeature(i_feat).EdgeFlag)) < 0)
							throw new Exception();
					}
				}
				else if (nab is qeq)
				{
					qeq qeq = (qeq)nab;
					if (qeq.IsCoreferenced || qeq.rgav.Count == 0)
						throw new Exception();

					String[] rgs = qeq.rgav.Keys.ToArray();
					f = ftm.im.GetMaximalTypeForFeature(_get_feat(rgs[0])).EdgeFlag;
					for (int i = 1; i < rgs.Length; i++)
					{
						int i_feat = _get_feat(rgs[i]);
						//if (!ftm.tm.UnifyInTypeNoCoref(ref f, ftm.tm.GetMaximalTypeForFeature(i_feat).EdgeFlag))
						if ((f = ftm.tu.UnifyTypesFull(f, ftm.im.GetMaximalTypeForFeature(i_feat).EdgeFlag)) < 0)
							throw new Exception();
					}
				}
				else if (ht is atomic_string)
				{
					String sub, nq = ((atomic_string)ht).NoQuotes;
					if (rel_name_map != null && rel_name_map.TryGetValue(nq, out sub))
						nq = sub;
					int sid;
					Type t;

#if PREFER_STRINGS
					if ((sid = ftm.td.strings[nq]) != -1)
						f = EdgeFlag.EtmStringValue | (EdgeFlag)sid;
					else if (ftm.td.TryGetType(nq, out t))
						f = t.EdgeFlag;
					else
						throw new Exception();
#else
					if (ftm.td.TryGetType(nq, out t))
						f = t.EdgeFlag;
					else if ((sid = ftm.td.strings[nq]) != -1)
						f = EdgeFlag.EtmStringValue | (EdgeFlag)sid;
					else
						throw new Exception();
#endif
				}
				else if (!ftm.tu.TryGetTypeFlag(ht.Label, out f))
				{
					String msg = String.Format("Couldn't get type '{0}' from grammar '{1}'", ht.Label, ftm.Name);
					throw new Exception(msg);
				}

				int nm;
				if (nab.IsCoreferenced)
				{
					f |= EdgeFlag.Coreference;

					if (!var_to_mark.TryGetValue(nab, out nm))
					{
						nm = next_coref_mark--;
						var_to_mark.Add(nab, nm);
					}
				}
				else
					nm = (f & EdgeFlag.EtmNonBareType) != 0 ? next_mark++ : 0;

				if (nab is variable)
				{
					if (nm == 0)
						throw new Exception();
					arr_tfs_entry ate = default(arr_tfs_entry);
					ate.mark = (short)nm;

					ate.i_feat = _get_feat("instloc");
					if (add_skolems)
						ate.e = new Edge(((variable)nab).SkolemValue, 0);
					else
						ate.e = new Edge(ftm.td.ΔString.EdgeFlag, 0);
					callback(ate);
				}

				if (nab is relation)
				{
					arr_tfs_entry ate = default(arr_tfs_entry);
					ate.mark = (short)nm;
					ate.i_feat = _get_feat("pred");
					ate.e = _get_edge(nab.HostType);
					callback(ate);
				}

				if ((f & EdgeFlag.EtmNonBareType) != 0)
					walk(nm, nab);

				return new Edge(f, nm);
			}
		};
	};
#endif
}